package com.rockbb.common.utilities.file;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.log4j.Logger;

import com.csvreader.CsvReader;
import com.enterprisedt.net.ftp.FTPConnectMode;
import com.enterprisedt.net.ftp.FTPException;
import com.enterprisedt.net.ftp.FTPFile;
import com.enterprisedt.net.ftp.FTPTransferType;
import com.enterprisedt.net.ftp.FileTransferClient;
import com.rockbb.common.utilities.security.StringDigest;

/**
 * 文件处理工具类
 * 
 */
public class FileUtil {

	
	private static Logger logger = Logger.getLogger(FileUtil.class);

	/**
	 * 删除指定文件
	 * 
	 * @param path
	 * @return
	 */
	public static boolean deleteFile(String path) {
		try {
			File file = new File(path);
			return file.delete();
		} catch(SecurityException e) {
			return false;
		}
	}

	/**
	 * Initialize a FileTransferClient instance
	 * 
	 * @param host
	 * @param port
	 * @param username
	 * @param password
	 * @param mode
	 * @param timeout_seconds
	 * @return
	 */
	public static FileTransferClient initializeFTPClient(
			String host, 
			int port, 
			String username, 
			String password,
			FTPConnectMode mode,
			int timeout_seconds)
	{
		String ftpInfo = "FTP: " + host + ":" + port + "   " + username + ":" + password;
		FileTransferClient ftp = new FileTransferClient();
		try 
		{
			ftp.setRemoteHost(host);
			ftp.setRemotePort(port);
			ftp.setUserName(username);
			ftp.setPassword(password);
			ftp.getAdvancedFTPSettings().setConnectMode(mode);
			ftp.setTimeout(timeout_seconds * 1000);
			ftp.connect();
			ftp.setContentType(FTPTransferType.BINARY);
			logger.info("FTP Connected");
		}
		catch (FTPException e) 
		{
			logger.info("FTPException. "+ ftpInfo);
			logger.debug(e.getMessage(), e);
			return null;
		}
		catch (IOException e) 
		{
			logger.info("IOException. "+ ftpInfo);
			logger.debug(e.getMessage(), e);
			return null;
		}
		catch (Exception e) 
		{
			logger.info("Unknown Exception. "+ ftpInfo);
			logger.debug(e.getMessage(), e);
			return null;
		}
		return ftp;
	}

	/**
	 * Close a FileTransferClient instance
	 * 
	 * @param client
	 */
	public static void closeFTPClient(FileTransferClient client)
	{
		if (client == null || !client.isConnected())
		{
			logger.info("FileTransferClient is null or disconnected.");
			return;
		}
		try
		{
			client.disconnect();
		}
		catch (IOException e) 
		{
			logger.info("IOException in closing FTP client.");
			logger.debug(e.getMessage(), e);
		}
		catch (Exception e) 
		{
			logger.info("Unknown Exception in closing FTP client.");
			logger.debug(e.getMessage(), e);
		}
	}

	/**
	 * Upload a local file to the remote FTP server
	 * 
	 * @param client
	 * @param localFilePath the local file path, including full file name 
	 * @param serverPath the path on server side, ended with '/'
	 * @param serverFileName
	 * @return
	 */
	public static boolean uploadLocalFileToFTP(FileTransferClient client, String localFilePath, String serverPath, String serverFileName)
	{
		if (client == null || !client.isConnected())
		{
			logger.info("FileTransferClient is null or disconnected.");
			return false;
		}

		if (serverPath.length() < 2) 
		{ 
			serverPath = "/";
		}
		else if (serverPath.charAt(serverPath.length() - 1) != '/')
		{
			serverPath += '/';
		}

		try
		{
			if (!verifyFtpDirecroty(serverPath, client))
			{
				logger.info("FTP Failed to verify the server side path: " + serverPath);
			}
			// Try to upload the file anyway, because some FTP servers restrict the directory operations
			client.uploadFile(localFilePath, serverPath+serverFileName);
			logger.info("FTP uploaded successfully: " + serverPath+serverFileName);
		}
		catch(FTPException e)
		{
			logger.info("FTPException when uploading to : " + serverPath);
			logger.debug(e.getMessage(), e);
			return false;
		}
		catch(IOException e)
		{
			logger.info("IOException when uploading to : " + serverPath);
			logger.debug(e.getMessage(), e);
			return false;
		}
		return true;
	}

	/**
	 * 获取FTP目标文件夹下的所有以某扩展名结尾的文件的路径
	 * 
	 * @param path
	 * @param extension
	 * @return
	 */
	public static List<Map<String, Object>> getAllFtpFiles(
			FileTransferClient ftp, 
			String path, 
			String extension, 
			String ignorePath, 
			boolean exploreSubFolders
	) throws IOException{

		List<Map<String, Object>> allFilePaths = new ArrayList<Map<String, Object>>();
		// 删除掉路径后面多余的"/"
		if (path.charAt(path.length()-1) == '/') {path = path.substring(0, path.length()-1);}

		try {
			for (FTPFile f : ftp.directoryList(path)) {
				//logger.debug(f.toFormattedString());
				if (f.isFile() && extension.equalsIgnoreCase(getExtension(f.getName()))) {
					Map<String, Object> fileInfo = new HashMap<String, Object>();
					String filepath = path+"/"+f.getName();
					fileInfo.put("filepath", filepath);
					Date date = ftp.getModifiedTime(filepath);
					fileInfo.put("date", date);
					allFilePaths.add(fileInfo);
				} else if (exploreSubFolders && f.isDir()) {
					String subPath = path +"/" + f.getName();
					if (subPath.equals(ignorePath)) {continue;}
					List<Map<String, Object>> subFilePaths = getAllFtpFiles(ftp, subPath, extension, ignorePath, exploreSubFolders);
					if (subFilePaths != null && subFilePaths.size() > 0) {
						allFilePaths.addAll(subFilePaths);
					}
				}

			}
		} catch (FTPException e) {
			logger.info("FTPException");
			logger.debug(e.getMessage(),e);
		} catch (ParseException e) {
			logger.info("ParseException");
			logger.debug(e.getMessage(),e);
		}
		return allFilePaths;
	}

	/**
	 * 获取目标文件夹下的所有以某扩展名结尾的文件的路径
	 * 
	 * @param path
	 * @param extension
	 * @return
	 */
	public static List<String> getAllFiles(String path, String extension) {

		File dirFile = new File(path);
		File[] files = dirFile.listFiles();
		List<String> allFilePaths = new ArrayList<String>();

		if(files == null){
			return allFilePaths;
		}

		for (int i = 0; i < files.length; i++) {
			if (files[i].isFile()) {
				if (extension!=null && extension.length() > 0) {
					// 扩展名过滤
					String fileName = files[i].getName();
					if (extension.equalsIgnoreCase(getExtension(fileName))) {
						allFilePaths.add(files[i].getAbsolutePath());
					}
				} else {
					allFilePaths.add(files[i].getAbsolutePath());
				}
				
			} else if (files[i].isDirectory()) {
				List<String> subFiles = getAllFiles(files[i].getAbsolutePath(), extension);
				if (subFiles != null && subFiles.size() > 0) {
					allFilePaths.addAll(subFiles);
				}
			}
		}
		return allFilePaths;
	}

	/**
	 * 获取文件扩展名
	 * 
	 * @param filename
	 * @return
	 */
	public static String getExtension(String filename) {
		String defExt = ""; 

		if ((filename != null) && (filename.length() > 0)) {
			int i = filename.lastIndexOf('.');

			if ((i >-1) && (i < (filename.length() - 1))) {
				defExt = filename.substring(i + 1);
			}
		}
		return defExt;
	}

	/**
	 * 获取文件最后修改时间
	 * @param filename
	 * @return
	 */
	public static Date getLastModified(String filename) {
		File file = new File(filename);
		long lastModified = file.lastModified();
		return new Date(lastModified);
	}

	/**
	 * 移动文件至新目录
	 * @param srcFilePath 原文件地址(包含文件名)
	 * @param destPath 新文件路径(不包含文件名)
	 * @param destFileName 新文件名(不包含路径), 为空则使用原文件名
	 */
	public static void moveFile(String srcFilePath, String destPath, String destFileName) {
		File srcFile = new File(srcFilePath);
		moveFile(srcFile, destPath, destFileName);
	}

	public static void moveFile(File file, String destPath, String destFileName) {
		File destFolder = new File(destPath);
		if (!destFolder.isDirectory()) {
			destFolder.mkdirs();
		}

		if (destFileName == null || destFileName.length() == 0) {destFileName = file.getName();}
		File destFile = new File(destPath + destFileName);
		file.renameTo(destFile);
	}

	public static boolean verifyFtpDirecroty(String path, FileTransferClient ftpClient) throws IOException{

		String[] paths = path.split("/");
		String current_pathname = "";
		for (int i=0; i<paths.length; i++) {
			if (paths[i].length() == 0) continue;
			current_pathname += "/"+paths[i];
			boolean flag = true;
			try {
				if (!ftpClient.existsDirectory(current_pathname)) {
					ftpClient.createDirectory(current_pathname);
				}
			} catch (Exception e) {
				logger.info("Error in making directory: "+current_pathname);
				flag = false;
			}
			if (!flag) return false;
		}
		return true;
	}

	public static List<String[]> parseCsv(InputStream is, char delimiter, String encoding)
	{
		List<String[]> list = new ArrayList<String[]>();
		CsvReader reader = null;
		try 
		{
			//初始化CsvReader并指定列分隔符和字符编码
			reader = new CsvReader(is, delimiter, Charset.forName(encoding));
			while (reader.readRecord()) 
			{
				String[] str = reader.getValues();
				if (str != null && str.length > 0) 
				{
					if (str[0] != null && !"".equals(str[0].trim())) 
					{
						list.add(str);
					}
				}
			}
		} 
		catch (IOException e) 
		{
			logger.error("parse csv input stream.");
			logger.debug(e.getMessage(), e);
		} 
		finally 
		{
			if (reader != null) reader.close();
		}
		return list;
	}
	
	/**
	 * 读取CSV文件, 返回以字符串数组为元素的列表
	 * 
	 * @param file 文件路径
	 * @param delimiter 分隔符
	 * @param encoding 编码, 一般使用GBK, UTF-8 或 Big5 就可以
	 * @return
	 */
	public static List<String[]> parseCsv(String file, char delimiter, String encoding) 
	{
		List<String[]> list = new ArrayList<String[]>();
		CsvReader reader = null;
		try 
		{
			//初始化CsvReader并指定列分隔符和字符编码
			reader = new CsvReader(file, delimiter, Charset.forName(encoding));
			while (reader.readRecord()) 
			{
				String[] str = reader.getValues();
				if (str != null && str.length > 0) 
				{
					if (str[0] != null && !"".equals(str[0].trim())) 
					{
						list.add(str);
					}
				}
			}
		} 
		catch (FileNotFoundException e) 
		{
			logger.error("Error reading csv file:"+ file);
			logger.debug(e.getMessage(), e);
		} 
		catch (IOException e) 
		{
			logger.error("parse csv file error:"+ file);
			logger.debug(e.getMessage(), e);
		} 
		finally {
			if (reader != null) reader.close();
		}
		return list;
	}

	/**
	 * 转为使用动态页面, 废除路径
	 * 
	 * @param number
	 * @return
	 */
	public static String genSubPaths(long number)
	{
		return "";
	}

	/**
	 * 新的32进制路径生成函数, 也是2层目录, 相比较10进制路径要短很多
	 * "1gdn25nl" = 52000003829L
	 * 
	 * @param number
	 * @return
	 */
	public static String genSubPaths__(long number)
	{
		String hex32 = StringDigest.toUnsignedString(number, 5);
		if (hex32.length() < 6)
			hex32 = String.format("%1$0"+(6-hex32.length())+"d%2$s", 0, hex32);
		int length = hex32.length();
		StringBuffer sb = new StringBuffer();
		sb.append(hex32.substring(0, length-4));
		sb.append('/');
		sb.append(hex32.substring(length-4, length-2));
		sb.append('/');
		return sb.toString();
	}

	/**
	 * 路径生成函数, 为避免过多文件处于同一目录, 提高文件系统检索效率, 需要将文件分目录树存储. 
	 * 机制: 给定数字, 如果小于9位自动扩展至9位, 从个位起, 每3位作为一个目录组, 共2层目录.
	 * 高于9位的不再分层. 适用于文件总数在10亿级别的处理. 
	 * 例: 
	 * IN: 1, OUT: 00/000/000/; IN: 3399678, OUT: 00/003/399/; IN: 2000003829, OUT: 02/000/003/
	 * 
	 * @param number
	 * @return
	 */
	public static String genSubPaths_(long number)
	{
		String extend_string = String.format("%09d", number);
		int length = extend_string.length();
		StringBuffer sb = new StringBuffer();
		sb.append(extend_string.substring(0, length-6));
		sb.append('/');
		sb.append(extend_string.substring(length-6, length-3));
		sb.append('/');
		return sb.toString();
	}

	public static void main(String[] args)
	{
		long[] nums = {0, 1, 12, 30, 31, 32, 33, 1335, 1336, 1337, 1338, 2678, 12856, 3399678, 2000003829, 52000003829L};
		for (long num : nums)
		{
			System.out.println(genSubPaths(num));
		}
	}
}
